#include <exception>
#include "cvGabor.h"
#include "LBPExtractor.h"
#include "../CommonUsed/TMatrixDef.h"
#include "LGBPExtractor.h"
#include "../CommonUsed/TMatrix.h"

class RefHolder
{
public:
	static void DeleteAll()
	{
		if (NULL != lbpExtractor)
		{
			delete lbpExtractor;
		}
		for ( int iScale=0; iScale<GABOR_SCALE; iScale++ )
			for ( int iOrient=0; iOrient<GABOR_ORIENT; iOrient++ )
			{
				if (NULL != gaborFilters[iScale*GABOR_ORIENT + iOrient])
				{
					delete gaborFilters[iScale*GABOR_ORIENT + iOrient];
				}
			}
	}

	static void InitLBPExtractor(int overlapping, int nBlocksX, int nBlocksY)
	{
		m_iBlockX = nBlocksX;
		m_iBlockY = nBlocksY;
		m_overlapping = overlapping;
		if (NULL != lbpExtractor)
		{
			delete lbpExtractor;
		}
		lbpExtractor = new LBPExtractor(overlapping, nBlocksX, nBlocksY);
	}

	static LBPExtractor* GetLBPExtractor(void)
	{
		if (NULL == lbpExtractor)
		{
			InitLBPExtractor(m_overlapping, m_iBlockX, m_iBlockY);
		}
		return lbpExtractor;
	}

	static void InitGaborFilter(void)
	{
		for ( int iScale=0; iScale<GABOR_SCALE; iScale++ )
			for ( int iOrient=0; iOrient<GABOR_ORIENT; iOrient++ )
			{
				if (NULL != gaborFilters[iScale*GABOR_ORIENT + iOrient])
				{
					delete gaborFilters[iScale*GABOR_ORIENT + iOrient];
				}
				gaborFilters[iScale*GABOR_ORIENT + iOrient] = CGaborFilter::GenerateGaborFilter(iScale, iOrient);
			}
	}

	static CGaborFilter* GetGaborFilter(int index)
	{
		if (NULL == gaborFilters[index])
		{
			InitGaborFilter();
		}
		return gaborFilters[index];
	}

private:
	static LBPExtractor* lbpExtractor;
	static CGaborFilter *gaborFilters[GABOR_ORIENT*GABOR_SCALE];
	static int m_iBlockX;
	static int m_iBlockY;
	static int m_overlapping;
};

LBPExtractor* RefHolder::lbpExtractor = NULL;
CGaborFilter* RefHolder::gaborFilters[GABOR_ORIENT*GABOR_SCALE] = {NULL};
int RefHolder::m_iBlockX = 0;
int RefHolder::m_iBlockY = 0;
int RefHolder::m_overlapping = 0;

LGBPExtractor::LGBPExtractor(int iBlockX, int iBlockY, FAST::TMatrix<MappingFeatureType> *matAlpha, int featureSelection)
{
	m_matAlpha = matAlpha;
	outsideAlpha = true;
	Init(iBlockX, iBlockY, featureSelection);
}

LGBPExtractor::LGBPExtractor(int iBlockX, int iBlockY, char* alphaFileName, int featureSelection)
{
	m_matAlpha = new FAST::TMatrix<MappingFeatureType>();
	m_matAlpha->Read(alphaFileName,true);
	outsideAlpha = false;
	Init(iBlockX, iBlockY, featureSelection);
}

void LGBPExtractor::Init( int iBlockX, int iBlockY, int featureSelection )
{
	m_iBlockX = iBlockX;
	m_iBlockY = iBlockY;
	m_featureSelection = featureSelection;
	m_matFeatures = new FAST::TMatrix<FeatureType>();
	outMat = NULL;
	RefHolder::InitGaborFilter();
	RefHolder::InitLBPExtractor(0, iBlockX, iBlockY);
}

LGBPExtractor::~LGBPExtractor(void)
{
	if (!outsideAlpha)
	{
		delete m_matAlpha;
	}
	delete outMat;
	RefHolder::DeleteAll();
}

int LGBPExtractor::ExtractFeatures(IplImage* pSrcImg, int faceOrient)
{
	IplImage	*pImgGray[MULTILEVELS];
	int			scaleFactor[MULTILEVELS];
	int			imageWidth,	imageHeight;
	FeatureType	*featureBuffer[MULTILEVELS];
	int			featureSize[MULTILEVELS];
	int			totalSize = 0;
	scaleFactor[0] = 1;
	for(int i=1;i<MULTILEVELS;i++)
	{
		scaleFactor[i] = scaleFactor[i-1]*2; 
	}
	for(int i=0;i<MULTILEVELS;i++)
	{
		imageWidth = pSrcImg->width/scaleFactor[i];
		imageHeight = pSrcImg->height/scaleFactor[i];
		//blockWidth = (0 == imageWidth % m_iBlockX)?(imageWidth/m_iBlockX):(imageWidth/m_iBlockX + 1);
		//blockHeight = (0 == imageHeight % m_iBlockY)?(imageHeight/m_iBlockY):(imageHeight/m_iBlockY + 1);
		featureSize[i] = nBinNum*m_iBlockX*m_iBlockY*GABOR_SCALE*GABOR_ORIENT;
		featureBuffer[i] = new FeatureType[featureSize[i]];
		
		totalSize += featureSize[i];
		if(0==i){
			if (3 == pSrcImg->nChannels)
			{
				pImgGray[0] = cvCreateImage(cvSize(imageWidth, imageHeight),IPL_DEPTH_8U, 1);
				cvCvtColor(pSrcImg,pImgGray[0],CV_BGR2GRAY);
			}
			else
			{
				pImgGray[0] = pSrcImg;
			}
		} else {
			pImgGray[i] = cvCreateImage(cvSize(imageWidth, imageHeight),IPL_DEPTH_8U, 1);
			pImgGray[i]->origin = pSrcImg->origin;
			cvResize(pImgGray[0],pImgGray[i]);
		}

		cvEqualizeHist(pImgGray[i], pImgGray[i]);
		cvSmooth(pImgGray[i],pImgGray[i]);
		ExtractFeaturesOnSingleLevel(pImgGray[i], imageWidth, imageHeight, featureBuffer[i], faceOrient);
	}
	m_matFeatures->NewSize(1, totalSize);
	FeatureType* dst = m_matFeatures->GetRowPtr(0);
	for(int i=0;i<MULTILEVELS;i++)
	{
		memcpy(dst,featureBuffer[i],featureSize[i]*sizeof(FeatureType));
		dst += featureSize[i];
		delete featureBuffer[i];
	}
	for(int i=1;i<MULTILEVELS;i++)
		cvReleaseImage(pImgGray+i);

	return 1;
}

FAST::TMatrix<MappingFeatureType>* LGBPExtractor::getOutMat()
{
	return outMat;
}

FAST::TMatrix<FeatureType>* LGBPExtractor::getRawMat()
{
	return m_matFeatures;
}

void LGBPExtractor::MakeMappingTestFile(int nBinNum)
{
	FAST::TMatrix<FeatureType> *matData = m_matFeatures;
	if (m_matAlpha->GetCols() != nBinNum)
	{
		throw new std::exception("ERROR: alpha file format not match with bin_number");
		return;
	}
	if (m_matAlpha->GetRows() * nBinNum != matData->GetCols())
	{
		throw new std::exception("ERROR: data file not match with alpha file");
		return;
	}
	if(NULL == outMat)
	{
		outMat = new FAST::TMatrix<MappingFeatureType>(matData->GetRows(), matData->GetCols()/nBinNum);
	}
	else
	{
		outMat->Zero();
	}
	int maxRow = matData->GetRows();
	int maxBin = matData->GetCols()/nBinNum;
	for (int row=0; row<maxRow; ++row)
	{
		for (int bin=0; bin<maxBin; ++bin)
		{
			(*outMat)(row,bin) = 0;
			for (int i=0; i<nBinNum; ++i)
			{
				(*outMat)(row,bin) += (*m_matAlpha)(bin,i) * (*matData)(row, bin*nBinNum+i);
			}
		}
	}
}

void LGBPExtractor::ExtractFeaturesOnSingleLevel( IplImage* pImage, int iW, int iH, FeatureType* pFeatureBuff, int faceOrient )
{
	int iSubFeatureLen = nBinNum*m_iBlockX*m_iBlockY;//m_BlockFeature.GetFeatureLen();
	int totalGaborOrients = GABOR_ORIENT;
	if (m_featureSelection)
	{
		totalGaborOrients = GABOR_ORIENT/2;
	}
	else
	{
		faceOrient = _PM00;
	}

	CvMat* dst = cvCreateMat(pImage->height, pImage->width, CV_32F);
	for (int iScale=0; iScale<GABOR_SCALE; iScale++)
	{
		for (int iOrient=0; iOrient<totalGaborOrients; iOrient++)
		{
			unsigned uIndex,uIndex2;
			if (m_featureSelection)
			{
				uIndex = iScale * GABOR_ORIENT + GABOR_ORIENTs[faceOrient][iOrient];
			}
			else
			{
				uIndex = iScale * GABOR_ORIENT + iOrient;
			}
			uIndex2 = iScale * totalGaborOrients + iOrient;

			CGaborFilter* filter =  RefHolder::GetGaborFilter(uIndex); 
			unsigned uPos = 0;
			cvGaborConv(pImage, dst, filter);

			FeatureType* pSubBuf = pFeatureBuff + uIndex2 * iSubFeatureLen;
			RefHolder::GetLBPExtractor()->Extract(dst, pSubBuf);
		}
	}
	cvReleaseMat(&dst);
}
